﻿<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>HTML5 Canvas英文字母变化粒子动画特效</title>

<style>
body {
    margin:0;
    background:black;
    color:white;
    font-size:100px;
    font-family: Arial, Helvetica, sans-serif;
}
canvas {
    position:absolute;
    left:0;
    top:0;
    margin:0;
}
</style>
</head>
<body>

<canvas id="stage"></canvas>

<script>
function foreach(array, callback) {
 for(let ind=0; ind<array.length; ind++) {
  callback(array[ind],ind);
 }
} 
function rand(min,max) {
 return Math.floor(Math.random()*(max-min+1)+min);
}

class Dot {
    constructor(canvas,position) {
        this.canvas = canvas;
        this.context = this.canvas.getContext('2d');
        this.color = '#fff';
        this.position = {x:position.x,y:position.y};
        this.originalPosition = {x:position.x,y:position.y};
        this.reachPosition = {x:position.x, y:position.y};

        this.randomValue = rand(5,10);
        this.glitch = false;
        this.size = 1;
        this.mouse = {x:0,y:0};
        this.counter = 0;
    }

    update() {
        this.glitch = rand(0,200) < this.randomValue;
        let easing = this.randomValue / 50;

        this.position.x += ( this.reachPosition.x - this.position.x ) * easing;
        this.position.y += ( this.reachPosition.y - this.position.y ) * easing;

        let sin = Math.sin(this.counter * easing / 10);
        this.position.x += sin;
        this.size = sin > 1 ? sin : 1;

        this.counter++;
        this.draw();
    }

    draw() {
        this.context.beginPath();
        this.context.fillStyle = this.color;
        this.context.arc(this.position.x,this.position.y,this.size,0,Math.PI*2);
        this.context.fill();
        this.context.closePath();
    }
}

class Letter {
    constructor(canvas, text) {
        this.canvas = canvas;
        this.context = this.canvas.getContext('2d');
        this.size = '200px';
        this.color = 'rgb(0,255,0)';
        this.text = text;
        this.mouse = {x:0,y:0};
        this.position = [];
    }

    update() {  }

    // 返回的是包含着字体像素x,y坐标的数组
    getPixel() {

        this.position = [];

        this.context.clearRect(0,0,this.canvas.width,this.canvas.height);
        this.draw();
        let datas = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height);
        let x = 0;
        let y = 0;
        
        for (let i=0;i<datas.data.length;i+=4) {
            if(datas.data[i] == 0 && datas.data[i+1] == 255 && datas.data[i+2] == 0) {
                this.position.push({x:x,y:y});
            }

            x++;
            if(x >= this.canvas.width) {
                x = 0;
                y++;
            }

            this.context.putImageData(datas,0,i);
        }

        return this.position;
    }

    draw() {
        this.context.beginPath();
        this.context.fillStyle = this.color;
        this.context.font = this.size + ' Gerstner, Arial, sans-serif';
        this.context.fillText(this.text, (this.canvas.width / 2), (this.canvas.height / 2));
        this.context.textAlign = "center";
        this.context.textBaseline = "middle";
        this.context.fill();
        this.context.closePath();
    }
}
class Scene {
    constructor(opt) {
        this.canvas = document.querySelector(opt.canvas);
        this.context = this.canvas.getContext('2d');
        this.number = 25;
        this.counter = 0;
        this.mouse = {
            position:{x:0,y:0},
            plan: {x:0,y:0}
        };

        
        this.render = this.render.bind(this);
        this._mouseMove = this._mouseMove.bind(this);
        this._resize = this._resize.bind(this);
        
        window.addEventListener('mousemove',this._mouseMove);
        window.addEventListener('resize',this._resize);
        
        this.init(opt);
        this.currentLetter = this.letters[0];

        this.interval = setInterval( () => {
            this.counter++;

            if(this.counter >= this.letters.length) this.counter = 0;
            this.currentLetter = this.letters[this.counter];

        },1000);

        this.render();
    }

    init(opt) {
        this.canvas.width = opt.width;
        this.canvas.height = opt.height;
        this.dots = [];
        this.letters = [];

        let size = { width:this.canvas.width/this.number, height:this.canvas.height/this.number };
   
        for(let x=1; x<this.number; x++) {
            for(let y=1; y<this.number; y++) {
                this.dots.push(new Dot(this.canvas,{ x: size.width * x, y: size.height * y }));
            }
        }

        let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        for(let letter in alphabet) {
            this.letters.push(new Letter(this.canvas,alphabet[letter]));
        }
    }

    render() {
        this.context.clearRect(0,0,this.canvas.width, this.canvas.height);

        if( this.currentLetter.position.length == 0 ) {
            this.currentLetter.getPixel();
        }

        foreach( this.dots, (dot, ind) => {

            dot.mouse = this.mouse;
            dot.update();

            let divideBy = Math.floor(this.currentLetter.position.length / (this.dots.length - 1));
            
            let position = this.currentLetter.position[ind * divideBy];

            if(!position) return false;

            dot.reachPosition = { x: position.x, y: position.y };
        });

        requestAnimationFrame(this.render);
    }

    _mouseMove(e) {

        let width = (this.canvas.width / 2);
        let height = (this.canvas.height / 2);

        this.mouse.position = {
            x: e.clientX,
            y: e.clientY
        }

        this.mouse.plan.x = -((width - e.clientX) / width);
        this.mouse.plan.y = (height - e.clientY) / height;
    }

    _resize(e) {
        let opt = {width:window.innerWidth,height:window.innerHeight};
        this.init(opt);
    }
}


new Scene({
    canvas:'#stage',
    width:window.innerWidth,
    height:window.innerHeight
})
</script>

</body>
</html>